package com.gitee.wsl.prism4j.languages

import com.gitee.wsl.prism4j.GrammarUtils
import com.gitee.wsl.prism4j.GrammarUtils.findToken
import com.gitee.wsl.prism4j.GrammarUtils.require
import com.gitee.wsl.prism4j.Prism4j
import com.gitee.wsl.prism4j.Prism4j.Companion.grammar
import com.gitee.wsl.prism4j.Prism4j.Companion.pattern
import com.gitee.wsl.prism4j.Prism4j.Companion.token
import com.gitee.wsl.prism4j.annotations.Extend
import com.gitee.wsl.prism4j.Prism4j.Pattern

@Suppress("unused")
@Extend("clike")
object Prism_kotlin {
    fun create(prism4j: Prism4j): Prism4j.Grammar {
        val kotlin = GrammarUtils.extend(
            require(prism4j, "clike"),
            "kotlin",
            object : GrammarUtils.TokenFilter {
                override fun test(token: Prism4j.Token): Boolean {
                    return "class-name" != token.name()
                }
            },
            token(
                "keyword",
                pattern(
                    Pattern.compile("(^|[^.])\\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\\b"),
                    true
                )
            ),
            token(
                "function",
                pattern(Pattern.compile("\\w+(?=\\s*\\()")),
                pattern(Pattern.compile("(\\.)\\w+(?=\\s*\\{)"), true)
            ),
            token(
                "number",
                pattern(Pattern.compile("\\b(?:0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\\d+(?:_\\d+)*(?:\\.\\d+(?:_\\d+)*)?(?:[eE][+-]?\\d+(?:_\\d+)*)?[fFL]?)\\b"))
            ),
            token(
                "operator",
                pattern(Pattern.compile("\\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\\/*%<>]=?|[?:]:?|\\.\\.|&&|\\|\\||\\b(?:and|inv|or|shl|shr|ushr|xor)\\b"))
            )
        )

        GrammarUtils.insertBeforeToken(
            kotlin, "string",
            token("raw-string", pattern(Pattern.compile("(\"\"\"|''')[\\s\\S]*?\\1"), false, false, "string"))
        )

        GrammarUtils.insertBeforeToken(
            kotlin, "keyword",
            token(
                "annotation",
                pattern(Pattern.compile("\\B@(?:\\w+:)?(?:[A-Z]\\w*|\\[[^\\]]+\\])"), false, false, "builtin")
            )
        )

        GrammarUtils.insertBeforeToken(
            kotlin, "function",
            token("label", pattern(Pattern.compile("\\w+@|@\\w+"), false, false, "symbol"))
        )

        // this grammar has 1 token: interpolation, which has 2 patterns
        val interpolationInside: Prism4j.Grammar?
        run {
            // okay, I was cloning the tokens of kotlin grammar (so there is no recursive chain of calls),
            // but it looks like it wants to have recursive calls
            // I did this because interpolation test was failing due to the fact that `string`
            // `raw-string` tokens didn't have `inside`, so there were not tokenized
            // I still find that it has potential to fall with stackoverflow (in some cases)
            val tokens: MutableList<Prism4j.Token> = ArrayList(kotlin.tokens().size + 1)
            tokens.add(token("delimiter", pattern(Pattern.compile("^\\$\\{|\\}$"), false, false, "variable")))
            tokens.addAll(kotlin.tokens())
            interpolationInside = grammar(
                "inside",
                token(
                    "interpolation",
                    pattern(Pattern.compile("\\$\\{[^}]+\\}"), false, false, null, grammar("inside", tokens)),
                    pattern(Pattern.compile("\\$\\w+"), false, false, "variable")
                )
            )
        }

        val string = findToken(kotlin, "string")
        val rawString = findToken(kotlin, "raw-string")

        if (string != null
            && rawString != null
        ) {
            val stringPattern = string.patterns().get(0)
            val rawStringPattern = rawString.patterns().get(0)

            string.patterns().add(
                pattern(
                    stringPattern.regex(),
                    stringPattern.lookbehind(),
                    stringPattern.greedy(),
                    stringPattern.alias(),
                    interpolationInside
                )
            )

            rawString.patterns().add(
                pattern(
                    rawStringPattern.regex(),
                    rawStringPattern.lookbehind(),
                    rawStringPattern.greedy(),
                    rawStringPattern.alias(),
                    interpolationInside
                )
            )

            string.patterns().removeAt(0)
            rawString.patterns().removeAt(0)
        } else {
            throw RuntimeException(
                "Unexpected state, cannot find `string` and/or `raw-string` tokens " +
                        "inside kotlin grammar"
            )
        }

        return kotlin
    }
}
